/* 
 * Copyright (C) 2010 Christian Schneider
 * 
 * This file is part of NS2 news.
 * 
 * NS2 news is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * NS2 news is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with NS2 news.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package tritop.android.naturalselectionnews;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.util.Log;

public class WorkManager extends Service implements IGLOBALS{
	
	public static final String WARSTATSPARMA="displayendgamestats";
	public static final String KILLSTATSPARMA="statkilldata";
	public static final String TASK_UPDATE_STATS="STATUPDATE";
	public static final String TASK_UPDATE_TWEET="TWEETUPDATE";
	public static final int STATSWORKERMSGID=2000;
	public static final int NEWSWORKERMSGID=3000;
	private static final String LOGTAG="WorkManager";
	private final IBinder mBinder = new localBinder();
	private DBHelper mDBConn;
	private boolean isUpdatingStats=false;
	private boolean isUpdatingNews=false;
	private boolean isBound=false;
	private Handler mHandler=null,mCallbackHandler=null;
	private int mStatsWorkerActive;
	private int mNewsWorkerActive;
	private int mNewestBuild=NEWESTBUILD;
	private String mStatsBaseUrl;
	private String mNewsBaseUrl;
	
	private static volatile PowerManager.WakeLock wLock=null;

	synchronized private static PowerManager.WakeLock getWakeLock(Context ctx) {
		if (wLock==null) {
			PowerManager pMgr=(PowerManager)ctx.getSystemService(Context.POWER_SERVICE);
			wLock=pMgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"WorkManager");
			wLock.setReferenceCounted(true);
		}
		return(wLock);
	}
	
	
	@Override
	public void onRebind(Intent intent) {
		if(DEBUG_ON){
			Log.e(LOGTAG,"on Rebind");
		}
		isBound=true;
		super.onRebind(intent);
	}


	//*************************************************************************
	// we lost connection to app, it was propably closed, so stop working
	//*************************************************************************
	
	@Override
	public boolean onUnbind(Intent intent) {
		if(DEBUG_ON){
			Log.e(LOGTAG,"on unbind");
		}
		isBound=false;
		mCallbackHandler=null;
		autoStop();
		return true;
	}


	@Override
	public IBinder onBind(Intent arg0) {
		if(DEBUG_ON){
			Log.e(LOGTAG,"on bind");
		}
		isBound=true;
		return mBinder;
	}

	public class localBinder extends Binder{
		public WorkManager getService(){
			return WorkManager.this;
		}
	}
	
	
	//*****************************************************************************
	// keeps a wake lock while updating data
	//*****************************************************************************
	
	public static void startWakelockedService(Context ctxt, Intent rintent) {
		if(DEBUG_ON){
			Log.e(LOGTAG,"startWakelockedService");
		}
		getWakeLock(ctxt).acquire();
		ctxt.startService(rintent);
	}
	
	@Override
	public void onCreate() {
		super.onCreate();
		sdLog("on create",true);
		mStatsWorkerActive=0;
		mNewsWorkerActive=0;
		mDBConn = new DBHelper(this);
		mDBConn.getMaxTwitterGuid();
		
		setupHandler();
	}

	@Override
	public void onDestroy() {
		sdLog("on destroy",true);
		closeDb();
		super.onDestroy();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		sdLog("on start command",true);
		SharedPreferences mSharedPref = PreferenceManager.getDefaultSharedPreferences(this);
		mNewestBuild=Integer.valueOf(mSharedPref.getString("prefBuildVersion", String.valueOf(NEWESTBUILD)));
		mStatsBaseUrl=mSharedPref.getString("prefStatsUpdateUrl", DEFAULT_STATS_URL);
		mNewsBaseUrl=mSharedPref.getString("preNewsfeedUpdateUrl", DEFAULT_NEWS_URL);
		
		if(intent.hasExtra("TASK") && intent.getStringExtra("TASK").equals(WorkManager.TASK_UPDATE_STATS)){
			sdLog("on start command update stats",true);
				updateStatsData();
				
		}
		else if(intent.hasExtra("TASK") && intent.getStringExtra("TASK").equals(WorkManager.TASK_UPDATE_TWEET)){
			sdLog("on start command update tweet",true);
			updateTwitterData();
				
		}
		
		return Service.START_REDELIVER_INTENT;
	}

	
	//*****************************************************************************
	// worker threads use this handler to notify when work is done
	//*****************************************************************************
	
	private void setupHandler(){
		mHandler = new Handler(){
			@Override
			public void handleMessage(Message msg) {
				switch(msg.what){
					case STATSWORKERMSGID:statsWorkerFinished();break;
					case NEWSWORKERMSGID:newsWorkerFinished(msg.arg1);break;
					default:super.handleMessage(msg);
				}
			}
		};
	}
	
	
	//*****************************************************************************
	// Update warstats and killstats
	//*****************************************************************************
	public void updateStatsData(){
		if(!isUpdatingStats){
			this.cleanupOldStats(DBLOGROTATEDAYS);
			isUpdatingStats=true;
			this.statsUpdateRunning(true);
			sdLog("update Stats.....",true);
			this.updateKillStats();
			this.updateWarStats();
		}
		else{
			sdLog("SKIPPING update Stats.....",true);
		}
	}
	
	
	//*****************************************************************************
	// update News(Tweets and RSS)
	//*****************************************************************************
	
	public void updateNewsData(){
		if(!isUpdatingNews){
			isUpdatingNews=true;
			sdLog("update Stats.....",true);
			this.updateRssData();
			this.updateTwitterData();
		}
		else{
			sdLog("SKIPPING update news.....",true);
		}
	}
	
	
	//*****************************************************************************
	// start a worker to fetch RSS news
	//*****************************************************************************
	
	public void updateRssData(){
		URL url;
		try {
			url = new URL(mNewsBaseUrl);
			sdLog("Starting worker "+mNewsBaseUrl,true);
			NewsWorker worker= new NewsWorker(url,NewsWorker.RSSNEWS,mDBConn,mHandler);
			worker.start();
			mNewsWorkerActive++;
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
	}
	
	
	//*****************************************************************************
	// start a worker to fetch Tweets
	//*****************************************************************************
	
	public void updateTwitterData(){
		URL url;
		try {
			this.tweetUpdateRunning();
			url = new URL(TWEETURL+mDBConn.getMaxTwitterGuid());
			sdLog("Starting worker "+TWEETURL+mDBConn.getMaxTwitterGuid(),true);
			NewsWorker worker= new NewsWorker(url,NewsWorker.TWITTERNEWS,mDBConn,mHandler);
			worker.start();
			mNewsWorkerActive++;
		} catch (MalformedURLException e) {
			e.printStackTrace();
			this.showTweets(mDBConn.getTweets());
		}
	}
	
	public void closeDb(){
		if(mDBConn!=null && (isBound==false)){
			mDBConn.close();
		}
	}
	
	public void setmNewestBuild(int mNewestBuild) {
		this.mNewestBuild = mNewestBuild;
	}
	
	
	//*************************************************************************
	// this handler will be used to notify the app about usefull things
	//*************************************************************************
	
	public void setCallbackHandler(Handler handler){
		mCallbackHandler=handler;
		//showTweets(mDBConn.getTweets());
	}
	
	
	//*************************************************************************
	// get tweets from db
	//*************************************************************************
	
	public String[] fetchTweets(){
		if(DEBUG_ON){
			Log.e(LOGTAG,"tweet fetching");
		}
		return mDBConn.getTweets();
	}
	
	public boolean isUpdatingStats(){
		return isUpdatingStats;
	}
	
	
	//*************************************************************************
	// Stats worker notified us it has finished if nothing else is working 
	// release wake lock
	//*************************************************************************
	
	private void statsWorkerFinished(){
		mStatsWorkerActive--;
		if(mStatsWorkerActive<=0 ){
			isUpdatingStats=false;
			this.statsUpdateRunning(false);
			if(wLock!=null && wLock.isHeld() ){
				sdLog("Wake lock releasing stats",true);
				getWakeLock(this).release();
				//wLock=null;
			}
			autoStop();
		}
	}
	
	
	//*************************************************************************
	// News worker notified us it has finished if nothing else is working 
	// release wake lock
	//*************************************************************************
	
	private void newsWorkerFinished(int returncode){
		mNewsWorkerActive--;
		if(returncode==2){
			this.showTweetNotification();
			//showTweets(mDBConn.getTweets());
		}
		if(mNewsWorkerActive<=0){
			isUpdatingNews=false;
			this.showTweets(mDBConn.getTweets());
			if(wLock!=null && wLock.isHeld() ){
				sdLog("Wake lock releasing rss",true);
				getWakeLock(this).release();
				//wLock=null;
			}
			autoStop();
		}
	}
	
	//*************************************************************************
	// Start a worker for every known map to fetch kill statistics
	//*************************************************************************
	
	private void updateKillStats(){
		for(String map:MAPNAMES){
			String urlstr=mStatsBaseUrl+"/"+KILLSTATSPARMA+"?version="+mNewestBuild+"&map="+map;
			URL url;
			try {
				url = new URL(urlstr);
				sdLog("Starting worker "+urlstr,true);
				StatsWorker worker = new StatsWorker(url, StatsWorker.KILLSTATSPARSER, mDBConn,mHandler);
				worker.start();
				mStatsWorkerActive++;
			} catch (MalformedURLException e) {
				e.printStackTrace();
			}
		}
		
	}
	
	
	//*************************************************************************
	// Start a worker for every known map to fetch win statistics
	//*************************************************************************
	
	private void updateWarStats(){
		for(String map:MAPNAMES){
			String urlstr=mStatsBaseUrl+"/"+WARSTATSPARMA+"?version="+mNewestBuild+"&map="+map+"&output=json";
			URL url;
			try {
				url = new URL(urlstr);
				sdLog("Starting worker "+urlstr,true);
				StatsWorker worker = new StatsWorker(url, StatsWorker.WARSTATSPARSER, mDBConn,mHandler);
				worker.start();
				mStatsWorkerActive++;
			} catch (MalformedURLException e) {
				e.printStackTrace();
			}
		}
	}
	
	
	//*************************************************************************
	// We are out of work stop consuming cpu
	// 
	//*************************************************************************
	
	private void autoStop(){
		if(mNewsWorkerActive<=0 && mStatsWorkerActive<=0){
			this.closeDb();
			if(DEBUG_ON){
				Log.e("LOGTAG","autostop finished....");
			}
			this.stopSelf();
		}
	}
	
	
	//*************************************************************************
	// Tell bound app if we are still updating stats
	//*************************************************************************
	
	private void statsUpdateRunning(boolean isRunning){
		if(mCallbackHandler != null){
			if(DEBUG_ON){
				Log.e(LOGTAG,"stats update running "+isRunning);
			}
			Message msg = mCallbackHandler.obtainMessage();
			msg.what=TSAPad.UPDATESTATSREADY;
			msg.arg1=(isRunning)?1:0;
			msg.sendToTarget();
		}
	}
	
	
	//*************************************************************************
	// Tell bound app if we are still updating tweets
	//*************************************************************************
	
	private void tweetUpdateRunning(){
		if(mCallbackHandler != null){
			if(DEBUG_ON){
				Log.e(LOGTAG,"tweet update running");
			}
			Message msg = mCallbackHandler.obtainMessage();
			msg.what=TSAPad.UPDATETWEET;
			msg.sendToTarget();
		}
	}
	
	
	//*************************************************************************
	// Deliver latest tweets to bound app
	//*************************************************************************
	
	private void showTweets(String[] tweets){
		if(mCallbackHandler != null){
			if(DEBUG_ON){
				Log.e(LOGTAG,"UPDATING tweets"+tweets.length);
			}
			Message msg = mCallbackHandler.obtainMessage();
			msg.what=TSAPad.UPDATETWEETTEXT;
			msg.obj=tweets;
			msg.sendToTarget();
		}
	}
	
	
	//*************************************************************************
	// A new tweet was found, now create a notification
	//*************************************************************************
	
	private void showTweetNotification(){
		NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		Notification nInfo= new Notification(R.drawable.notification_ns2n,"New NS2 Tweet",System.currentTimeMillis());
		nInfo.flags |= Notification.FLAG_AUTO_CANCEL;
		Intent intent = new Intent(this,TSAPad.class);
		PendingIntent pendingInt = PendingIntent.getActivity(this, 999, intent, PendingIntent.FLAG_CANCEL_CURRENT);
		nInfo.setLatestEventInfo(this, "NS2 News", "New Tweet", pendingInt);
		mNotificationManager.notify(999, nInfo);
	}
	
	
	//*************************************************************************
	// delete stats data older than x days
	//*************************************************************************
	
	private void cleanupOldStats(int days){
		sdLog("logrotate "+days+" days",true);
		if(mDBConn != null){
			mDBConn.logrotate(days);
		}
	}
	
	
	public long getWins(int team,int build){
		long wins=0;
		if(mDBConn != null){
			wins=mDBConn.getTeamWins(team, build);
		}
		return wins;
	}
	
//*******************************************************
//Debug functions below this line
//*******************************************************
	
	public void sdLog(String log,boolean toLog){
		if(DEBUG_ON){
			if(toLog){
				Log.w(LOGTAG, log);
			}
			File logFile=new File(Environment.getExternalStorageDirectory(),"TSALOG.txt");
			DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
			Date date = new Date();
			try {
				FileWriter buf = new FileWriter(logFile,true);
				buf.append(dateFormat.format(date)+" "+log+"\n");
				buf.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	public void exportDb(){
		sdLog("Exporting stats DB",true);
		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
		File dbFile=getDatabasePath(DBHelper.DATABASE_NAME);
		
		try {
			int count;
			byte[] buffer = new byte[1024];
			BufferedInputStream bufinp=new BufferedInputStream(new FileInputStream(dbFile.getAbsolutePath()));
			BufferedOutputStream outFile=new BufferedOutputStream(new FileOutputStream(new File(Environment.getExternalStorageDirectory(),DBHelper.DATABASE_NAME)));
			while ((count = bufinp.read(buffer)) != -1){
    			outFile.write(buffer,0,count);
    		} 
			bufinp.close();
			outFile.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
